/** @file
  EFI PCAT ISA ACPI Driver for a Generic PC Platform

Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "PcatIsaAcpi.h"

//
//  PcatIsaAcpi Driver Binding Protocol
//
EFI_DRIVER_BINDING_PROTOCOL gPcatIsaAcpiDriverBinding = {
  PcatIsaAcpiDriverBindingSupported,
  PcatIsaAcpiDriverBindingStart,
  PcatIsaAcpiDriverBindingStop,
  0xa,
  NULL,
  NULL
};

/**
  the entry point of the PcatIsaAcpi driver.

  @param ImageHandle     Handle for driver image
  @param SystemTable     Point to EFI_SYSTEM_TABLE

  @return Success or not for installing driver binding protocol
**/
EFI_STATUS
EFIAPI
PcatIsaAcpiDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return EfiLibInstallDriverBindingComponentName2 (
           ImageHandle, 
           SystemTable, 
           &gPcatIsaAcpiDriverBinding,
           ImageHandle,
           &gPcatIsaAcpiComponentName,
           &gPcatIsaAcpiComponentName2
           );
}

/**
  ControllerDriver Protocol Method

  @param This                 Driver Binding protocol instance pointer.   
  @param Controller           Handle of device to test.
  @param RemainingDevicePath  Optional parameter use to pick a specific child
                              device to start.
  @retval EFI_SUCCESS         This driver supports this device.
  @retval other               This driver does not support this device.

**/
EFI_STATUS
EFIAPI
PcatIsaAcpiDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS           Status;
  EFI_PCI_IO_PROTOCOL  *PciIo;
  PCI_TYPE00           Pci;
  UINTN                SegmentNumber;
  UINTN                BusNumber;
  UINTN                DeviceNumber;
  UINTN                FunctionNumber;

  //
  // Get PciIo protocol instance
  //              
  Status = gBS->OpenProtocol (
                  Controller,  
                  &gEfiPciIoProtocolGuid, 
                  (VOID**)&PciIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR(Status)) {
    return Status;
  }

  Status = PciIo->Pci.Read (
                    PciIo,
                    EfiPciIoWidthUint32,
                    0,
                    sizeof(Pci) / sizeof(UINT32), 
                    &Pci);

  if (!EFI_ERROR(Status)) {
    Status = EFI_UNSUPPORTED;
    if ((Pci.Hdr.Command & 0x03) == 0x03) {
      if (Pci.Hdr.ClassCode[2] == PCI_CLASS_BRIDGE) {
        //
        // See if this is a standard PCI to ISA Bridge from the Base Code and Class Code
        //
        if (Pci.Hdr.ClassCode[1] == PCI_CLASS_BRIDGE_ISA) {
          Status = EFI_SUCCESS;
        } 

        //
        // See if this is an Intel PCI to ISA bridge in Positive Decode Mode
        //
        if (Pci.Hdr.ClassCode[1] == PCI_CLASS_BRIDGE_ISA_PDECODE && 
            Pci.Hdr.VendorId     == 0x8086                          ) {
          //
          // See if this is on Function #0 to avoid false positives on 
          // PCI_CLASS_BRIDGE_OTHER that has the same value as 
          // PCI_CLASS_BRIDGE_ISA_PDECODE
          //
          Status = PciIo->GetLocation (
                            PciIo, 
                            &SegmentNumber, 
                            &BusNumber, 
                            &DeviceNumber, 
                            &FunctionNumber
                            );
          if (!EFI_ERROR(Status) && FunctionNumber == 0) {
            Status = EFI_SUCCESS;
          } else {
            Status = EFI_UNSUPPORTED;
          }
        }
      } 
    }
  }

  gBS->CloseProtocol (
         Controller,       
         &gEfiPciIoProtocolGuid, 
         This->DriverBindingHandle,   
         Controller   
         );
  
  return Status;
}

/**
  Install EFI_ISA_ACPI_PROTOCOL.

  @param  This                 Driver Binding protocol instance pointer.
  @param  ControllerHandle     Handle of device to bind driver to.
  @param  RemainingDevicePath  Optional parameter use to pick a specific child
                               device to start.

  @retval EFI_SUCCESS          This driver is added to ControllerHandle
  @retval EFI_ALREADY_STARTED  This driver is already running on ControllerHandle
  @retval other                This driver does not support this device
**/
EFI_STATUS
EFIAPI
PcatIsaAcpiDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS           Status;
  EFI_PCI_IO_PROTOCOL  *PciIo;
  PCAT_ISA_ACPI_DEV    *PcatIsaAcpiDev;
  UINT64               Supports;
  UINT64               OriginalAttributes;
  BOOLEAN              Enabled;

  Enabled = FALSE;
  Supports = 0;
  PcatIsaAcpiDev = NULL;
  OriginalAttributes = 0;
  //
  // Open the PCI I/O Protocol Interface
  //
  PciIo = NULL;
  Status = gBS->OpenProtocol (
                  Controller,       
                  &gEfiPciIoProtocolGuid, 
                  (VOID**)&PciIo,
                  This->DriverBindingHandle,   
                  Controller,   
                  EFI_OPEN_PROTOCOL_BY_DRIVER 
                  );
  if (EFI_ERROR(Status)) {
    goto Done;
  }

  //
  // Get supported PCI attributes
  //
  Status = PciIo->Attributes (
                    PciIo,
                    EfiPciIoAttributeOperationSupported,
                    0,
                    &Supports
                    );
  if (EFI_ERROR(Status)) {
    goto Done;
  }

  Supports &= (UINT64) (EFI_PCI_IO_ATTRIBUTE_ISA_IO | EFI_PCI_IO_ATTRIBUTE_ISA_IO_16);
  if (Supports == 0 || Supports == (EFI_PCI_IO_ATTRIBUTE_ISA_IO | EFI_PCI_IO_ATTRIBUTE_ISA_IO_16)) {
    Status = EFI_UNSUPPORTED;
    goto Done;
  }

  Status = PciIo->Attributes (
                    PciIo,
                    EfiPciIoAttributeOperationGet,
                    0,
                    &OriginalAttributes
                    );
  if (EFI_ERROR(Status)) {
    goto Done;
  }

  Status = PciIo->Attributes (
                    PciIo, 
                    EfiPciIoAttributeOperationEnable, 
                    EFI_PCI_DEVICE_ENABLE | Supports | EFI_PCI_IO_ATTRIBUTE_ISA_MOTHERBOARD_IO, 
                    NULL 
                    );
  if (EFI_ERROR(Status)) {
    goto Done;
  }

  Enabled = TRUE;
  //
  // Allocate memory for the PCAT ISA ACPI Device structure
  //
  PcatIsaAcpiDev = NULL;
  Status = gBS->AllocatePool (
                  EfiBootServicesData,
                  sizeof(PCAT_ISA_ACPI_DEV),
                  (VOID**)&PcatIsaAcpiDev
                  );
  if (EFI_ERROR(Status)) {
    goto Done;
  }

  //
  // Initialize the PCAT ISA ACPI Device structure
  //
  PcatIsaAcpiDev->Signature          = PCAT_ISA_ACPI_DEV_SIGNATURE;
  PcatIsaAcpiDev->Handle             = Controller;
  PcatIsaAcpiDev->PciIo              = PciIo;
  PcatIsaAcpiDev->OriginalAttributes = OriginalAttributes;

  //
  // Initialize PcatIsaAcpiDeviceList
  //
  InitializePcatIsaAcpiDeviceList ();
  
  //
  // IsaAcpi interface
  //
  (PcatIsaAcpiDev->IsaAcpi).DeviceEnumerate  = IsaDeviceEnumerate;
  (PcatIsaAcpiDev->IsaAcpi).SetPower         = IsaDeviceSetPower;
  (PcatIsaAcpiDev->IsaAcpi).GetCurResource   = IsaGetCurrentResource;
  (PcatIsaAcpiDev->IsaAcpi).GetPosResource   = IsaGetPossibleResource;
  (PcatIsaAcpiDev->IsaAcpi).SetResource      = IsaSetResource;
  (PcatIsaAcpiDev->IsaAcpi).EnableDevice     = IsaEnableDevice;
  (PcatIsaAcpiDev->IsaAcpi).InitDevice       = IsaInitDevice;
  (PcatIsaAcpiDev->IsaAcpi).InterfaceInit    = IsaInterfaceInit;
    
  //
  // Install the ISA ACPI Protocol interface
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Controller,
                  &gEfiIsaAcpiProtocolGuid, &PcatIsaAcpiDev->IsaAcpi,
                  NULL
                  );

Done:
  if (EFI_ERROR(Status)) {
    if (PciIo != NULL && Enabled) {
      PciIo->Attributes (
               PciIo, 
               EfiPciIoAttributeOperationSet,
               OriginalAttributes,
               NULL 
               );
    }
    gBS->CloseProtocol (
           Controller, 
           &gEfiPciIoProtocolGuid, 
           This->DriverBindingHandle, 
           Controller
           );
    if (PcatIsaAcpiDev != NULL) {
      gBS->FreePool(PcatIsaAcpiDev);
    }
    return Status;
  }
          
  return EFI_SUCCESS;
}


/**
  Stop this driver on ControllerHandle. Support stopping any child handles
  created by this driver.

  @param  This              Protocol instance pointer.
  @param  ControllerHandle  Handle of device to stop driver on
  @param  NumberOfChildren  Number of Handles in ChildHandleBuffer. If number of
                            children is zero stop the entire bus driver.
  @param  ChildHandleBuffer List of Child Handles to Stop.

  @retval EFI_SUCCESS       This driver is removed ControllerHandle
  @retval other             This driver was not removed from this device

**/
EFI_STATUS
EFIAPI
PcatIsaAcpiDriverBindingStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                   *ChildHandleBuffer
  )
{
  EFI_STATUS             Status;
  EFI_ISA_ACPI_PROTOCOL  *IsaAcpi;
  PCAT_ISA_ACPI_DEV      *PcatIsaAcpiDev;
  
  //
  // Get the ISA ACPI Protocol Interface
  // 
  Status = gBS->OpenProtocol (
                  Controller, 
                  &gEfiIsaAcpiProtocolGuid, 
                  (VOID**)&IsaAcpi,
                  This->DriverBindingHandle,   
                  Controller,   
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR(Status)) {
    return Status;
  }

  //
  // Get the PCAT ISA ACPI Device structure from the ISA ACPI Protocol
  //
  PcatIsaAcpiDev = PCAT_ISA_ACPI_DEV_FROM_THIS (IsaAcpi);

  //
  // Restore PCI attributes
  //
  Status = PcatIsaAcpiDev->PciIo->Attributes (
                                    PcatIsaAcpiDev->PciIo,
                                    EfiPciIoAttributeOperationSet,
                                    PcatIsaAcpiDev->OriginalAttributes,
                                    NULL
                                    );
  if (EFI_ERROR(Status)) {
    return Status;
  }

  //
  // Uninstall protocol interface: EFI_ISA_ACPI_PROTOCOL
  //
  Status = gBS->UninstallProtocolInterface (
                  Controller,
                  &gEfiIsaAcpiProtocolGuid, &PcatIsaAcpiDev->IsaAcpi
                  );
  if (EFI_ERROR(Status)) {
    return Status;
  }

  gBS->CloseProtocol (
         Controller, 
         &gEfiPciIoProtocolGuid, 
         This->DriverBindingHandle, 
         Controller
         );
  
  gBS->FreePool(PcatIsaAcpiDev);
  
  return EFI_SUCCESS;
}
